#ifndef BLOCKQUEUE_H
#define BLOCKQUEUE_H

#include "../Locker/Locker.h"

#include <iostream>
#include <pthread.h>
#include <vector>
#include <atomic>
#include <sys/time.h>
#include <stdlib.h>

/**
 * @ TODO
 * size_ maxSize_ can be substituded by vector functions
*/

template <typename T>
class BlockQueue
{
public:
    BlockQueue(int maxSize = 1000);
    ~BlockQueue() = default;

    bool push(T& elem);
    bool pop(T& elem, int timeoutMs = 0);

    // @TODO: thread safe?
    const int size() const { return size_; }
    const int maxSize() const { return maxSize_; }

    bool isFull();
    bool isEmpty();

    bool front(T &elem);
    bool back(T &elem);

private:
    Locker mutex_;
    Cond cond_;

    std::vector<T> array_; // @FIXME: use vector 
    // std::atomic_int size_; 
    int size_;
    int maxSize_;
    int front_;
    int back_;
};

static int CheckSizeGreaterZero(int maxSize) {
    if(maxSize <= 0) {
        std::cout << __FILE__ << ':' << __FUNCTION__ << ':' << __LINE__ << ' ' << "maxSize <= 0" << std::endl;
        exit(-1);
    }
    return maxSize;
}

template <typename T>
inline BlockQueue<T>::BlockQueue(int maxSize)
    : maxSize_(CheckSizeGreaterZero(maxSize))
    , array_(maxSize_) // reverse maxSize elements
    , size_(0)
    , front_(-1)
    , back_(-1) {}

template <typename T>
inline bool BlockQueue<T>::push(T &elem)
{
    mutex_.lock();
    if(size_ >= maxSize_) {
        cond_.broadcast(); // notify consumers
        mutex_.unlock();
        return false;
    }

    back_ = (back_ + 1) % maxSize_;
    array_[back_] = elem;

    size_++; // array_.size();
    cond_.broadcast();
    mutex_.unlock();
    return true;
}

template <typename T>
inline bool BlockQueue<T>::pop(T &elem, int timeoutMs)
{
    if(timeoutMs == 0) {
        mutex_.lock();
        while(size_ <= 0) { // while not if
            if(!cond_.wait(mutex_.get())) {
                mutex_.unlock();
                return false;
            }
        }
    }else {
        struct timespec t = {0, 0};
        struct timeval now = {0, 0};
        gettimeofday(&now, NULL);

        mutex_.lock();
        if(size_ <= 0) {
            t.tv_sec = now.tv_sec + timeoutMs / 1000;
            t.tv_nsec = (timeoutMs % 1000) * 1000;
            if(!cond_.timewait(mutex_.get(), t)) {
                mutex_.unlock();
                return false;
            }
        }

        if(size_ <= 0) {
            mutex_.unlock();
            return false;
        }
    }
    
    front_ = (front_ + 1) % maxSize_;
    elem = array_[front_];
    size_--;
    mutex_.unlock();
    return true;
}

template <typename T>
inline bool BlockQueue<T>::isFull()
{
    //return size_ >= maxSize_;
    mutex_.lock();
    if(size_ >= maxSize_) {
        mutex_.unlock();
        return true;
    }
    mutex_.unlock();
    return false;
}

template <typename T>
inline bool BlockQueue<T>::isEmpty()
{
    // return 0 == size_;
    mutex_.lock();
    if(0 == size_) {
        mutex_.unlock();
        return true;
    }
    mutex_.unlock();
    return false;
}

template <typename T>
inline bool BlockQueue<T>::front(T &elem)
{
    mutex_.lock();
    if(0 == size_) {
        mutex_.unlock();
        return false;
    }
    elem = array_[front_];
    mutex_.unlock();
    return false;
}

template <typename T>
inline bool BlockQueue<T>::back(T &elem)
{
    mutex_.lock();
    if(0 == size_) {
        mutex_.unlock();
        return false;
    }
    elem = array_[back_];
    mutex_.unlock();
    return false;
}

#endif